package org.longs.util;

import java.util.ArrayList;
import java.util.List;

public class MapDecompression {


    private final byte[] data;

    private int recorder = 0;
    private int index = 0;
    private int type = 1;

    public MapDecompression(byte[] data) {
        this.data = data;
    }


    public List<Byte> unZipData() {
        List<Byte> unzipData = new ArrayList<>();
        initType(unzipData);
        while (true) {
            if (selectType(unzipData)) continue;
            if (getBytes(unzipData)) {
                return unzipData;
            }
        }
    }

    private boolean selectType(List<Byte> unzipData) {
        int check;
        if (type == 1) {
            recorder = data[index++] & 0xFF;
            if (recorder < 16) {
                if (recorder == 0) {
                    loopCopy();
                    recorder = recorder + 15 + data[index++] & 0xFF;
                }
                recorder = recorder - 1 + 4;
                while (recorder != 0) {
                    unzipData.add(data[index++]);
                    recorder--;
                }
                type = 2;
            }
        }
        if (type == 2) {
            type = 1;
            recorder = data[index++] & 0xFF;
            if (recorder < 16) {
                check = unzipData.size() - 0x0801 - (recorder >> 2) - (data[index++] << 2);
                unzipData.add(unzipData.get(check++));
                unzipData.add(unzipData.get(check++));
                unzipData.add(unzipData.get(check));
                return copyData(unzipData);
            }
        } else {
            type = 1;
        }
        return false;
    }

    private void loopCopy() {
        while (data[index] == 0) {
            recorder = recorder + 255;
            index++;
        }
    }

    private boolean copyData(List<Byte> unzipData) {
        recorder = data[index - 2] & 3;
        if (recorder == 0) {
            return true;
        }
        while (recorder != 0) {
            unzipData.add(data[index++]);
            recorder--;
        }
        recorder = data[index++] & 0xFF;
        return false;
    }

    private void initType(List<Byte> unzipData) {
        if (data[index] > 17) {
            recorder = (data[index++] & 0xFF) - 17;
            if (recorder < 4) {
                while (recorder != 0) {
                    unzipData.add(data[index++]);
                    recorder--;
                }
                recorder = data[index++] & 0xFF;
                type = 0;
            } else {
                while (recorder != 0) {
                    unzipData.add(data[index++]);
                    recorder--;
                }
                type = 2;
            }
        }
    }

    private boolean getBytes(List<Byte> op) {
        int check;
        while (true) {
            if (recorder >= 64) {
                check = op.size() - 1 - ((recorder >> 2) & 7) - ((data[index] & 0xFF) << 3);
                index++;
                recorder = (recorder >> 5) + 1;
                while (recorder != 0) {
                    op.add(op.get(check++));
                    recorder--;
                }
                if (copyData(op)) break;
                continue;
            } else if (recorder >= 32) {
                recorder = recorder & 31;
                if (recorder == 0) {
                    loopCopy();
                    recorder = recorder + 31 + (data[index++] & 0xFF);
                }
                check = op.size() - 1 - ((((data[index] & 0xFF) | ((data[index + 1] & 0xFF) << 8))) >> 2);
                index = index + 2;
            } else if (recorder >= 16) {
                check = op.size() - ((recorder & 8) << 11);
                recorder = recorder & 7;
                if (recorder == 0) {
                    loopCopy();
                    recorder = recorder + 7 + (data[index++] & 0xFF);
                }
                check = check - (((data[index] & 0xFF) | ((data[index + 1] & 0xFF) << 8)) >> 2);
                if (check == op.size()) {
                    return true;
                }
                index += 2;
                check = check - 0x4000;
            } else {
                check = op.size() - 1 - (recorder >> 2) - ((data[index++] & 0xFF) << 2);
                op.add(op.get(check++));
                op.add(op.get(check));
                if (copyData(op)) break;
                continue;
            }
            recorder = recorder + 2;
            while (recorder != 0) {
                op.add(op.get(check++));
                recorder--;
            }
            if (copyData(op)) break;
        }
        return false;
    }

}
